using System;
using BrainTechLLC;
using BrainTechLLC.ThreadSafeObjects;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using System.Runtime.Serialization;
using System.Diagnostics;

namespace AltSerialize
{
    public static class Util
    {
        /// <summary>
        /// Gets an array of bytes from any primitive object type.
        /// </summary>
        /// <param name="obj">Object to retrieve bytes from</param>
        /// <param name="objectType">The object type passed in <paramref name="obj"/>.</param>
        /// <returns>Returns an array of bytes.</returns>
        public static byte[] GetBytes(object obj, Type objectType)
        {
            if (objectType == typeof(int)) return BitConverter.GetBytes((int)obj);
            else if (objectType == typeof(long)) return BitConverter.GetBytes((long)obj);
            else if (objectType == typeof(bool)) return new byte[] { (bool)obj == true ? (byte)1 : (byte)0 };
            else if (objectType == typeof(double)) return BitConverter.GetBytes((double)obj);
            else if (objectType == typeof(byte)) return new byte[] { (byte)obj };
            else if (objectType == typeof(sbyte)) return new byte[] { (byte)((sbyte)obj) };
            else if (objectType == typeof(short)) return BitConverter.GetBytes((short)obj);
            else if (objectType == typeof(ushort)) return BitConverter.GetBytes((ushort)obj);
            else if (objectType == typeof(uint)) return BitConverter.GetBytes((uint)obj);
            else if (objectType == typeof(ulong)) return BitConverter.GetBytes((ulong)obj);
            else if (objectType == typeof(float)) return BitConverter.GetBytes((float)obj);
            else if (objectType == typeof(char)) return BitConverter.GetBytes((char)obj);
            else if (objectType == typeof(IntPtr)) throw new AltSerializeException("IntPtr type is not supported.");
            else if (objectType == typeof(UIntPtr)) throw new AltSerializeException("UIntPtr type is not supported.");
            else
                throw new AltSerializeException("Could not retrieve bytes from the object type " + objectType.FullName + ".");
        }

        /// <summary>
        /// Turns an array of bytes into the specified object type.
        /// </summary>
        /// <param name="bytes">Array of bytes containing the object data.</param>
        /// <param name="objectType">The type of object to convert byte array to.</param>
        /// <returns>Returns an object of the type specified in <paramref name="objectType"/>.</returns>
        public static object ReadBytes(byte[] bytes, Type objectType)
        {
            if (objectType == typeof(bool)) return bytes[0] == (byte)1 ? true : false;
            else if (objectType == typeof(byte)) return bytes[0];
            else if (objectType == typeof(int)) return BitConverter.ToInt32(bytes, 0);
            else if (objectType == typeof(long)) return BitConverter.ToInt64(bytes, 0);
            else if (objectType == typeof(double)) return BitConverter.ToDouble(bytes, 0);
            else if (objectType == typeof(sbyte)) return (sbyte)bytes[0];
            else if (objectType == typeof(short)) return BitConverter.ToInt16(bytes, 0);
            else if (objectType == typeof(ushort)) return BitConverter.ToUInt16(bytes, 0);
            else if (objectType == typeof(uint)) return BitConverter.ToUInt32(bytes, 0);
            else if (objectType == typeof(ulong)) return BitConverter.ToUInt64(bytes, 0);
            else if (objectType == typeof(float)) return BitConverter.ToSingle(bytes, 0);
            else if (objectType == typeof(char)) return BitConverter.ToChar(bytes, 0);
            else if (objectType == typeof(IntPtr))
                throw new AltSerializeException("IntPtr type is not supported.");

            throw new AltSerializeException("Could not retrieve bytes from the object type " + objectType.FullName + ".");
        }

        // Write 24-bit unsigned int
        public static void WriteUInt24(this Stream s, int value)
        {
            s.WriteByte((byte)(value & 0xFF));
            s.WriteByte((byte)(((value >> 8) & 0xFF)));
            s.WriteByte((byte)(((value >> 16) & 0xFF)));
        }

        public static void WriteUInt16(this Stream s, int value)
        {
            s.WriteByte((byte)(value & 0xFF));
            s.WriteByte((byte)(((value >> 8) & 0xFF)));
        }

        /// <summary>
        /// Writes a byte to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, byte val)
        {
            s.WriteByte(val);
        }

        /// <summary>
        /// Writes a signed byte to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, sbyte val)
        {
            s.WriteByte((byte)val);
        }

        /// <summary>
        /// Writes an array of bytes to the serialization stream.
        /// </summary>
        /// <param name="bytes">Array of bytes to write.</param>
        /// <param name="offset">Offset to begin writing from.</param>
        /// <param name="count">Number of bytes to write.</param>
        public static void Write(this Stream s, byte[] bytes, int offset, int count)
        {
            s.Write(bytes, offset, count);
        }

        /// <summary>
        /// Writes an array of bytes to the serialization stream.
        /// </summary>
        /// <param name="bytes">Array of bytes to write.</param>
        public static void Write(this Stream s, byte[] bytes)
        {
            s.Write(bytes, 0, bytes.Length);
        }

        /// <summary>
        /// Writes a signed 32-bit value to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, Int32 value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 4);
        }

        /// <summary>
        /// Writes an unsigned 32-bit value to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, UInt32 value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 4);
        }

        /// <summary>
        /// Writes an unsigned 64-bit value to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, UInt64 value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 8);
        }

        public static void Write(this Stream s, long value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 8);
        }

        /// <summary>
        /// Writes a DateTime to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, DateTime value)
        {
            long binary = value.Ticks;
            Write(s, binary);
        }

        /// <summary>
        /// Writes a TimeSpan to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, TimeSpan value)
        {
            long binary = value.Ticks;
            Write(s, binary);
        }

        /// <summary>
        /// Writes a Guid to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, Guid value)
        {
            Write(s, value.ToByteArray());
        }

        /// <summary>
        /// Writes a Decimal to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, Decimal value)
        {
            int[] bits = Decimal.GetBits(value);
            Write(s, bits[0]);
            Write(s, bits[1]);
            Write(s, bits[2]);
            Write(s, bits[3]);
        }

        /// <summary>
        /// Writes a double-precision floating point value to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, double value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 8);
        }

        /// <summary>
        /// Writes a single-precision floating point value to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, float value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 4);
        }

        /// <summary>
        /// Writes a Char to the serialization stream.
        /// </summary>
        public static void Write(this Stream s, char value)
        {
            s.Write(BitConverter.GetBytes(value), 0, 2);
        }

        /// <summary>
        /// Writes a CultureInfo structure to the serialization stream.
        /// </summary>
        public static void WriteCultureInfo(System.Globalization.CultureInfo info)
        {
        }

        // Read 24-bit unsigned int
        private static int ReadUInt24(this Stream s)
        {
            // Read first two bytes.
            int newValue;
            newValue = s.ReadByte();
            newValue += (s.ReadByte() << 8);
            newValue += (s.ReadByte() << 16);
            return newValue;
        }

        /// <summary>
        /// Reads a Byte from the serialization stream.
        /// </summary>
        public static int ReadByte(this Stream s)
        {
            return s.ReadByte();
        }

        /// <summary>
        /// Reads a signed byte from the serialization stream.
        /// </summary>
        public static sbyte ReadSByte(this Stream s)
        {
            return (sbyte)s.ReadByte();
        }

        /// <summary>
        /// Reads an array of bytes from the serialization stream.
        /// </summary>
        /// <param name="count">Number of bytes to read.</param>
        /// <returns>Returns an array of bytes read from the deserialization stream.</returns>
        public static byte[] ReadBytes(this Stream s, int count)
        {
            byte[] ret = new byte[count];
            ReadBytes(s, ret, 0, count);
            return ret;
        }

        /// <summary>
        /// Reads an array of bytes from the serialization stream.
        /// </summary>
        /// <param name="bytes">Byte array to read into.</param>
        /// <param name="offset">Starting offset of <paramref>bytes</paramref>.</param>
        /// <param name="count">Number of bytes to read.</param>
        public static void ReadBytes(this Stream s, byte[] bytes, int offset, int count)
        {
            s.Read(bytes, offset, count);
        }
        
        /// <summary>
        /// Reads culture info from the serialization stream.
        /// </summary>
        public static System.Globalization.CultureInfo ReadCultureInfo()
        {
            return System.Globalization.CultureInfo.InvariantCulture;
        }
    }
}
